package cn.tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**本类用于测试反射*/
public class TestReflect {
    /**单元测试方法的格式：public + void + 无参 +@Test */
    //1.通过单元测试方法，获取目标类Student对应的字节码对象----假装Student类不是我们写的
    @Test
    public void getClazz() throws ClassNotFoundException {
        //练习获取字节码对象的三种方式
        Class<?> clazz1 = Class.forName("cn.tedu.reflection.Student");//Class.forName("类的全路径")
        Class<?> clazz2 = Student.class;//类名.class
        Class<?> clazz3 = new Student().getClass();//对象.getClass()

        //打印获取到的字节码对象
        System.out.println(clazz1);//class cn.tedu.reflection.Student
        System.out.println(clazz2);//class cn.tedu.reflection.Student
        System.out.println(clazz3);//class cn.tedu.reflection.Student

        //通过刚刚获取到的字节码对象，获取Student类的资源
        //获取字节码对象对应的全路径名
        System.out.println(clazz1.getName());//cn.tedu.reflection.Student
        //获取字节码对象对应的类的名字
        System.out.println(clazz1.getSimpleName());//Student
        //获取Student类(字节码对象类)对应的包对象
        System.out.println(clazz1.getPackage());//package cn.tedu.reflection
        //先获取Student类对应的包对象，再通过这个包对象，获取包名
        System.out.println(clazz1.getPackage().getName());//cn.tedu.reflection

    }

    //2.通过单元测试方法，获取Student类中的成员变量
    @Test
    public void getFieldd() throws ClassNotFoundException {
        //获取目标类对应的字节码对象
        Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
        //通过字节码对象获取成员变量们
        Field[] fs = clazz.getFields();
        //打印数组，查看数组中的内容，只有char类型的可以直接打印，其余的都需要通过Arrays.toString()
        System.out.println(Arrays.toString(fs));//[public int cn.tedu.reflection.Student.age,public int cn.tedu.reflection.Student.age2]
        //遍历数组，高效for循环，foreach，获取每个成员
        /**注意！目前成员变量的修饰符需要是public才能获取到，其他修饰符可以通过暴力反射获取*/
        for (Field f:fs) {
            System.out.println(f);//打印本类循环到的字段对象
            System.out.println(f.getName());//通过字段对象获取本字段的名字
            System.out.println(f.getType());//通过字段对象获取本字段的类型
        }

    }

    //3.通过单元测试方法，获取Student类中的成员方法
    @Test
    public void getFunction(){
        //获取目标类对应的字节码对象
        Class<?> clazz = Student.class;
        //通过字节码对象获取目标类中的成员方法们
        Method[] ms = clazz.getMethods();
        //打印查看方法数组对象的所有内容
        System.out.println(Arrays.toString(ms));
        //使用高效for循环遍历数组，使用每个具体的方法对象，查看当前方法的具体信息
        for (Method m:
             ms) {
            System.out.println(m.getName());//通过遍历到的方法对象，查看方法名
            //通过遍历到的方法对象，查看方法的参数类型
            System.out.println(Arrays.toString(m.getParameterTypes()));
            System.out.println("-----------************-------------");
        }
    }

    //4.通过单元测试方法，获取目标类Student中的所有构造方法们
    @Test
    public void getCons() throws ClassNotFoundException {
        //遍历获取到的构造函数们，分别获取每个构造函数方法名和方法参数，并打印
        //获取字节码对象
//        Class<?> c = Class.forName("cn.tedu.reflection.Student");
        Class<?> clazz1 = new Student().getClass();
        //通过字节码对象，获取目标类中的构造方法们
        Constructor<?>[] cons = clazz1.getConstructors();
        System.out.println(Arrays.toString(cons));
        //遍历获取到的构造函数们，分贝获取每个构造函数方法名和方法参数，并打印
        for (Constructor cc: cons) {
            System.out.println(cc.getName());
            System.out.println(Arrays.toString(cc.getParameterTypes()));
        }
    }

    //5.通过反射，创建目标类的对象
    @Test
    public void getObject() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //获取字节码对象
        Class<?> clazz = Student.class;
        //通过反射技术创建目标类的对象，注意抛出异常
        /**反射创建对象方案1：通过触发目标类中的无参构造创建对象*/
        Object o = clazz.newInstance();//多态的写法
        System.out.println(o);

        /**反射创建对象方案2：通过触发目标类的全参构造创建对象
         * 1.先获取指定的构造函数对象
         * 注意需要指定构造函数的参数，并且传入的是.class字节码对象*/
        //获取目标类中指定的那个全参构造对象---先获取字节码对象，然后获取构造函数
        //获取构造函数对象类，再使用newInstance()
        Constructor<?> c = clazz.getConstructor(String.class,int.class);//参数为字节码对象，使用类名.class创建字节码对象
        //通过获取到的构造函数：创建对象+给所有对象的属性赋值
        Object o2 = c.newInstance("赵六", 6);
        System.out.println(o2);
        //触发Student类中的第3个构造函数，创建对象o3
        //先获取到指定的构造函数对象
        Constructor<?> c2 = clazz.getConstructor(String.class, int.class, int.class);
        //使用这个刚获取到的构造函数对象，帮我们创建Student类的对象
        Object o3 = c2.newInstance("王五", 12,24);
        System.out.println(o3);
        System.out.println(Arrays.toString(c2.getParameterTypes()));

    }
}
